package com.yp.infrastructure.mvc.filter;


import com.yp.infrastructure.common.exception.BaseException;
import com.yp.infrastructure.common.exception.ErrorMessage;
import com.yp.infrastructure.common.exception.ErrorMessageUtils;
import com.yp.infrastructure.mvc.consts.EnumErrorMessage;
import com.yp.infrastructure.mvc.i18n.I18nMessageSourceService;
import com.yp.infrastructure.mvc.model.ResultSet;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.MessageFormat;
import java.util.*;

@Slf4j
@ControllerAdvice
public class ServletExceptionFilter {


    private static final String FIELD_ERROR = "{0} {1} - [{2}]";

    /**
     * 业务类异常处理，HttpCode=200
     *
     * @param be      be
     * @param request request
     * @return ResultSet
     */
    @ResponseBody
    @ExceptionHandler(BaseException.class)
    public ResultSet businessExceptionHandler(BaseException be, HttpServletRequest request) {
        String extra = getI18nMessage(request, be.getErrorMessage().getI18n());
        return ResultSet.builder()
                .errorMessage(ErrorMessage.builder()
                        .code(be.getErrorMessage().getCode())
                        .i18n(be.getErrorMessage().getI18n())
                        .msg(extra)
                        .build())
                .build();
    }


    /**
     * 未知异常
     *
     * @param e        exception
     * @param request  request
     * @param response response.
     * @return ResultSet
     */
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public ResultSet unknownExceptionHandler(Exception e,
                                             HttpServletRequest request,
                                             HttpServletResponse response) {
        log.error("type: {}, {}", e.getClass().getSimpleName(), e.getMessage());
        return handlerSysException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, EnumErrorMessage.SERVER_ERROR,
                Collections.singletonList(e.getLocalizedMessage()),
                request, response);
    }


    /**
     * Get/Put/Post用法不对
     *
     * @param e        exception
     * @param request  request
     * @param response response.
     * @return ResultSet
     */
    @ResponseBody
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultSet httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e,
                                                            HttpServletRequest request,
                                                            HttpServletResponse response) {
        return handlerSysException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                EnumErrorMessage.REQUEST_ERROR,
                Collections.singletonList(e.getLocalizedMessage()),
                request, response);
    }


    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    public ResultSet handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
                                                     HttpServletRequest request,
                                                     HttpServletResponse response
    ) {
        String unsupported = "Unsupported content type: " + ex.getContentType();
        String supported = "Supported content types: " + MediaType.toString(ex.getSupportedMediaTypes());
        return handlerSysException(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE,
                EnumErrorMessage.REQUEST_ERROR, Arrays.asList(unsupported, supported),
                request, response);
    }


    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public ResultSet handleMissingServletRequestParameterException(MissingServletRequestParameterException ex,
                                                                   HttpServletRequest request,
                                                                   HttpServletResponse response) {
        String error = ex.getParameterName() + ", " + ex.getMessage();
        return handlerSysException(HttpServletResponse.SC_BAD_REQUEST,
                EnumErrorMessage.REQUEST_ERROR, Collections.singletonList(error),
                request, response);

    }

    /**
     * 方法参数异常，如参数为整形，但用户提供了字符型.
     *
     * @param e        exception
     * @param request  request
     * @param response response
     * @return ResultSet
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResultSet methodArgumentNotValidExceptionHandler(MethodArgumentTypeMismatchException e,
                                                            HttpServletRequest request,
                                                            HttpServletResponse response) {
        return handlerSysException(HttpServletResponse.SC_BAD_REQUEST,
                EnumErrorMessage.PARAMETERS_ERROR,
                Collections.singletonList(e.getLocalizedMessage()),
                request, response);
    }


    /**
     * 方法参数异常，如参数为整形，但用户提供了字符型.
     *
     * @param ex       exception
     * @param response response
     * @return ResultSet
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultSet handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request,
                                                HttpServletResponse response) {
        List<String> extra = new ArrayList<>();
        List<ObjectError> list = ex.getBindingResult().getAllErrors();

        for (ObjectError error : list) {

            FieldError fieldError = (FieldError) error;

            String fieldName = fieldError.getField();
            if (StringUtils.isEmpty(fieldName)) continue;

            String i18nFieldNameKey = "field.name." + fieldName;
            String i18nFieldName = getI18nMessage(request, i18nFieldNameKey);
            if (StringUtils.isEmpty(i18nFieldName)) continue;

            String fieldErrorDefaultMessage = fieldError.getDefaultMessage();
            if (StringUtils.isEmpty(fieldErrorDefaultMessage)) continue;

            String[] fieldErrorMessages = fieldErrorDefaultMessage.split("-");
            String errorMessage = fieldErrorMessages[0];
            String i18nErrorMessage = getI18nMessage(request, errorMessage);
            if (StringUtils.isEmpty(i18nErrorMessage)) continue;

            if (fieldErrorMessages.length == 2) {
                i18nErrorMessage = String.format(i18nErrorMessage, fieldErrorMessages[1]);
            }

            if (fieldErrorMessages.length == 3) {
                i18nErrorMessage = MessageFormat.format(i18nErrorMessage, fieldErrorMessages[1], fieldErrorMessages[2]);
            }

            String tmp = MessageFormat.format(FIELD_ERROR, i18nFieldName, i18nErrorMessage, fieldError.getRejectedValue());
            extra.add(tmp);
        }

        return handlerSysException(HttpServletResponse.SC_BAD_REQUEST,
                EnumErrorMessage.PARAMETERS_ERROR,
                extra,
                request, response);

    }

    private ResultSet handlerSysException(int httpStatusCode,
                                          EnumErrorMessage enumErrorMessage,
                                          List<String> details,
                                          HttpServletRequest request,
                                          HttpServletResponse response) {
        response.setStatus(httpStatusCode);
        ErrorMessage errorMessage
                = ErrorMessageUtils.toErrorMessage(enumErrorMessage, getI18nMessage(request, enumErrorMessage.getI18n()));
        errorMessage.setExtra(details);
        return ResultSet.builder()
                .data(null)
                .errorMessage(errorMessage)
                .build();
    }

    private String getI18nMessage(HttpServletRequest request, String i18n) {
        Locale locale = request.getLocale();
        String strLocale = request.getHeader("Locale");
        if (StringUtils.isNotBlank(strLocale)) {
            strLocale = strLocale.replace("-", "_");
            String[] tmp = strLocale.split("_");
            if (tmp.length == 2) {
                locale = new Locale(tmp[0], tmp[1]);
            }
        }
        return I18nMessageSourceService.getInstance().getMessage(i18n, locale);
    }
}
